/*
 * Used for AK/SK Signature Authentication.
 *
 * Copyright (c) Huawei Technologies Co., Ltd. 2022. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 *
 * Lots of code copied from Huawei APIG SDK, and modified to be useful in qingtian_enclaves:
 * https://support.huaweicloud.com/devg-apisign/api-sign-sdk-c.html
 */
#ifdef _MSC_VER
#define strcasecmp stricmp
#endif
#include <string.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include "signer.h"
#define max(a, b) (((a) > (b)) ? (a) : (b))

#define SIG_HEADER_AUTHORIZATION        "Authorization"
#define SIG_HEADER_XDATE                "X-Sdk-Date"
#define SIG_HEADER_CONTENT_SHA256       "X-Sdk-Content-Sha256"
#define SIG_HEADER_HOST                 "Host"
#define SIG_SIGNING_ALGORITHM           "SDK-HMAC-SHA256"
#define SIG_AUTH_HEADER_MAX_LEN         1024
#define SIG_CANONICAL_REQUEST_BUF_LEN   4096
#define SIG_QUERY_BUF_LEN               1024
#define SIG_STRING_TO_SIGN_BUF_LEN      1024
#define SIG_KEY_BUF_LEN                 256
#define SIG_MAX_NUM_QUERY_COMPONENTS    50

#define DECODE_LEN_MAX_MULTIPLE         2
#define BASE_YEAR                       1900
#define HEX_LEFT_SHIFT                  4

sig_header_t *sig_headers_get(const sig_headers_t *headers, const char *header_name)
{
    size_t i;
    for (i = 0; i < headers->len; i++) {
        if (!strcasecmp(headers->data[i].name.data, header_name))
            return &headers->data[i];
    }
    return NULL;
}

sig_header_t *sig_headers_add(sig_headers_t *headers, const char *header_name,
                              const char *header_value)
{
    size_t len;
    size_t alloc;
    size_t new_alloc;
    size_t name_len;
    size_t value_len;

    len = headers->len;
    alloc = headers->alloc;
    if (len >= alloc) {
        new_alloc = max(alloc << 1, 2);
        sig_header_t* new_data = malloc(new_alloc * sizeof(sig_header_t));
        if (new_data == NULL)
            return NULL;

        if (headers->data != NULL) {
            memcpy(new_data, headers->data, alloc * sizeof(sig_header_t));
            free(headers->data);
        }
        headers->alloc = new_alloc;
        headers->data = new_data;
    }

    name_len = strlen(header_name);
    headers->data[len].name.len = name_len;
    headers->data[len].name.data = malloc(name_len + 1);
    if (headers->data[len].name.data == NULL)
        return NULL;

    strcpy(headers->data[len].name.data, header_name);

    value_len = strlen(header_value);
    headers->data[len].value.len = value_len;
    headers->data[len].value.data = malloc(value_len + 1);
    if (headers->data[len].value.data == NULL) {
        free(headers->data[len].name.data);
        return NULL;
    }

    strcpy(headers->data[len].value.data, header_value);

    headers->len = len + 1;
    return &headers->data[len];
}

void sig_headers_free(sig_headers_t *headers)
{
    size_t i;

    if (!headers)
        return;

    if (headers->data != NULL) {
        for (i = 0; i < headers->len; i++) {
            free(headers->data[i].name.data);
            free(headers->data[i].value.data);
            headers->data[i].name.data = NULL;
            headers->data[i].value.data = NULL;
        }
        free(headers->data);
        headers->data = NULL;
    }
    headers->len = 0;
    headers->alloc = 0;
}

void sig_params_init(sig_params_t *sig_params)
{
    if (sig_params)
        memset(sig_params, 0, sizeof(*sig_params));
}

void sig_params_free(sig_params_t *sig_params)
{
    if (sig_params)
        sig_headers_free(&sig_params->headers);
}

static void parse_query_components(const sig_str_t *query_str,
    char *query_buf, sig_str_t *query_component_arr, size_t *arr_len)
{
    char unescape_buf[SIG_QUERY_BUF_LEN] = {0};
    size_t idx = 0;
    char *last_ptr = NULL;
    char *c_ptr = NULL;
    size_t len;

    if (query_component_arr == NULL) {
        *arr_len = 0;
        return;
    }
    if (sig_empty_str(query_str)) {
        *arr_len = 0;
        return;
    }

    last_ptr = query_str->data;
    c_ptr = query_str->data;
    query_component_arr[0].data = query_buf;
    while (c_ptr != query_str->data + query_str->len) {
        if (*c_ptr == '&') {
            len = sig_unescape(unescape_buf, last_ptr, c_ptr - last_ptr);
            len = sig_escape(query_buf, unescape_buf, len, SIG_ESCAPE_QUERY);
            if (memchr(query_buf, '=', len) == NULL)
                query_buf[len++] = '=';

            query_component_arr[idx].len = len;
            query_component_arr[idx++].data = query_buf;
            query_buf += len;
            c_ptr++;
            last_ptr = c_ptr;
        } else {
            c_ptr++;
        }
    }
    len = sig_unescape(unescape_buf, last_ptr, c_ptr - last_ptr);
    len = sig_escape(query_buf, unescape_buf, len, SIG_ESCAPE_QUERY);
    if (memchr(query_buf, '=', len) == NULL)
        query_buf[len++] = '=';

    query_component_arr[idx].len = len;
    query_component_arr[idx++].data = query_buf;
    *arr_len = idx;
}

/*
 * get_hexdigest() - Get hex encoding of a given string.
 * @str_in (input): Input string.
 * @hex_out (output): Output buffer to store hex encoded string.
 *
 * Context: Process context.
 *
 * Returns: Void.
 */
static void get_hexdigest(const sig_str_t *str_in, sig_str_t *hex_out)
{
    size_t i;
    static const char digits[] = "0123456789abcdef";
    char* c_ptr = hex_out->data;
    for (i = 0; i < str_in->len; i++) {
        *(c_ptr++) = digits[((unsigned int)(str_in->data[i]) & 0xf0) >> HEX_LEFT_SHIFT];
        *(c_ptr++) = digits[(unsigned int)(str_in->data[i]) & 0x0f];
    }
    hex_out->len = str_in->len * DECODE_LEN_MAX_MULTIPLE;
}

/*
 * get_hex_sha256() - Get hex encoded sha256 of a given string.
 * @str_in (input): Input string.
 * @hex_sha256_out (output): Output buffer to store hex encoded sha256 string.
 *
 * Context: Process context.
 *
 * Returns: Void.
 */
static void get_hex_sha256(const sig_str_t *str_in, sig_str_t *hex_sha256_out)
{
    unsigned char sha256_buf[SHA256_DIGEST_LENGTH] = {0};
    SHA256_CTX ctx;
    SHA256_Init(&ctx);
    SHA256_Update(&ctx, str_in->data, str_in->len);
    SHA256_Final(sha256_buf, &ctx);

    sig_str_t sha256_str = {.data = (char*)sha256_buf, .len = SHA256_DIGEST_LENGTH};
    get_hexdigest(&sha256_str, hex_sha256_out);
}

static int sig_header_cmp(const sig_header_t *header1, const sig_header_t *header2)
{
    return strcasecmp(header1->name.data, header2->name.data);
}

/*
 * get_signed_headers() - Get signed headers string.
 * @sig_params (input): Pointer to a struct of sig parameters.
 * @signed_headers (output): Struct of buffer to store signed headers string.
 *
 * Context: Process context.
 *
 * Returns: Void.
 */
static void get_signed_headers(const sig_params_t *sig_params, sig_str_t *signed_headers)
{
    size_t i;
    const sig_headers_t* headers = &sig_params->headers;
    char *str = signed_headers->data;

    for (i = 0; i < headers->len; i++) {
        str += sig_sprintf(str, "%v;", &headers->data[i].name);
    }
    signed_headers->len = str - signed_headers->data - 1;
}

/*
 * get_canonical_headers() - Get canonical headers string.
 * @sig_params (input): Pointer to a struct of sig parameters.
 * @canonical_headers (output): Struct of buffer to store canonical headers string.
 *
 * Context: Process context.
 *
 * Returns: Void.
 */
static void get_canonical_headers(sig_params_t* sig_params,
    sig_str_t* canonical_headers)
{
    size_t i;
    sig_headers_t* headers = &sig_params->headers;
    char* str = canonical_headers->data;
    for (i = 0; i < headers->len; i++) {
        sig_str_t trim_value;
        sig_trim_str(&headers->data[i].value, &trim_value);
        str += sig_sprintf(str, "%v:%V\n", &headers->data[i].name, &trim_value);
    }
    canonical_headers->len = str - canonical_headers->data;
}

static int sig_querystr_cmp(const sig_str_t* str1, const sig_str_t* str2)
{
    char* s1 = str1->data;
    char* s2 = str2->data;
    int ret = 0;
    while (1) {
        char c1 = *s1 == '=' ? '\0' : *s1;
        char c2 = *s2 == '=' ? '\0' : *s2;
        ret = c1 - c2;
        if (ret || c1 == '\0')
            break;

        s1++;
        s2++;
    }
    /* query key is the same, compare query value */
    if (ret == 0) {
        if (*s1 == '=') s1++;
        if (*s2 == '=') s2++;

        while (1) {
            char c1 = *s1;
            char c2 = *s2;
            ret = c1 - c2;
            if (ret || c1 == '\0')
                break;

            s1++;
            s2++;
        }
    }
    return ret;
}

/*
 * get_canonical_request() - Get canonical request string.
 * @sig_params (input): Pointer to a struct of sig parameters.
 * @canonical_request (output): Struct of buffer to store canonical request string.
 *
 * Context: Process context.
 *
 * Returns: Void.
 */
static void get_canonical_request(sig_params_t *sig_params, sig_str_t *canonical_request)
{
    size_t i;
    size_t len;
    sig_str_t query_components[SIG_MAX_NUM_QUERY_COMPONENTS] = {0};
    char qeury_buf[SIG_QUERY_BUF_LEN] = {0};
    size_t query_num = 0;
    char* str = canonical_request->data;

    str += sig_sprintf(str, "%V\n", &sig_params->method);
    len = sig_escape(str, sig_params->uri.data, sig_params->uri.len, SIG_ESCAPE_URI);
    str += len;
    if (*(str - 1) != '/')
        *(str++) = '/';
    *(str++) = '\n';

    parse_query_components(&sig_params->query_str, qeury_buf, query_components, &query_num);
    qsort((void *)query_components, query_num, sizeof(sig_str_t),
          (int (*)(const void *, const void *))sig_querystr_cmp);
    for (i = 0; i < query_num; i++) {
        str += sig_sprintf(str, "%V", &query_components[i]);
        if (i != query_num - 1)
            *(str++) = '&';
    }
    *(str++) = '\n';

    sig_str_t canonical_headers = {.data = str};
    get_canonical_headers(sig_params, &canonical_headers);
    str += canonical_headers.len;
    *(str++) = '\n';

    sig_str_t signed_headers = {.data = str};
    get_signed_headers(sig_params, &signed_headers);
    str += signed_headers.len;
    *(str++) = '\n';

    sig_str_t hex_sha256 = {.data = str};
    sig_header_t* header_content_sha256 =
        sig_headers_get(&sig_params->headers, SIG_HEADER_CONTENT_SHA256);
    if (header_content_sha256 != NULL) {
        size_t len_tmp = header_content_sha256->value.len;
        memcpy(hex_sha256.data, header_content_sha256->value.data, len_tmp);
        hex_sha256.len = len_tmp;
    } else {
        get_hex_sha256(&sig_params->payload, &hex_sha256);
    }

    str += hex_sha256.len;

    canonical_request->len = str - canonical_request->data;
}

static void get_time(char* timestr, int strlen)
{
    time_t timep = time(NULL);
    struct tm* utc = gmtime(&timep);
    (void)snprintf(timestr, strlen, "%04d%02d%02dT%02d%02d%02dZ",
                   utc->tm_year + BASE_YEAR, utc->tm_mon + 1,
                   utc->tm_mday, utc->tm_hour, utc->tm_min, utc->tm_sec);
}

/*
 * get_string_to_sign() - Get string to sign.
 * @request_date (input) : A pointer to a struct of request date in ISO8601 format.
 * @canonical_request (input) : Pointer to a struct of precomputed canonical request.
 * @string_to_sign (output) : Struct of buffer to store string to sign.
 *
 * Context: Process context.
 *
 * Returns: Void.
 */
static void get_string_to_sign(const char* request_date,
    const sig_str_t* canonical_request,
    sig_str_t* string_to_sign)
{
    char* str = string_to_sign->data;
    str += sprintf(str, SIG_SIGNING_ALGORITHM"\n%s\n", request_date);

    sig_str_t hex_sha256 = {.data = str};
    get_hex_sha256(canonical_request, &hex_sha256);
    str += hex_sha256.len;

    string_to_sign->len = str - string_to_sign->data;
}

int sig_sign(sig_params_t* sig_params)
{
    char date[DATE_MAX_LEN] = {0};
    char auth_header[SIG_AUTH_HEADER_MAX_LEN] = {0};
    char canonical_request_buf[SIG_CANONICAL_REQUEST_BUF_LEN] = {0};
    char string_to_sign_buf[SIG_STRING_TO_SIGN_BUF_LEN] = {0};

    if (sig_params == NULL ||
        sig_empty_str(&sig_params->secret) ||
        sig_empty_str(&sig_params->key) ||
        sig_empty_str(&sig_params->method) ||
        sig_empty_str(&sig_params->uri) ||
        sig_empty_str(&sig_params->host))
        return SIG_INVALID_INPUT_ERROR;

    sig_headers_t *headers = &sig_params->headers;
    if (sig_headers_get(headers, SIG_HEADER_HOST) == NULL)
        sig_headers_add(headers, SIG_HEADER_HOST, sig_params->host.data);

    sig_header_t* header_date = sig_headers_get(headers, SIG_HEADER_XDATE);
    if (header_date == NULL) {
        get_time(date, DATE_MAX_LEN);
        sig_headers_add(headers, SIG_HEADER_XDATE, date);
    } else {
        strncpy(date, header_date->value.data, DATE_MAX_LEN);
    }

    qsort((void *)headers->data, headers->len, sizeof(sig_header_t),
          (int (*)(const void *, const void *))sig_header_cmp);

    /* SDK-HMAC-SHA256 Credential=AKIDEXAMPLE/<credential_scope> */
    char* str = auth_header;
    str += sig_sprintf(str, SIG_SIGNING_ALGORITHM" Access=%V", &sig_params->key);

    /* SignedHeaders=<signed_headers> */
    str += sig_sprintf(str, ", SignedHeaders=");
    sig_str_t signed_headers = {.data = str};
    get_signed_headers(sig_params, &signed_headers);
    str += signed_headers.len;

    /* Signature=<signature> */
    str += sig_sprintf(str, ", Signature=");
    /* Task 1: Create a canonical request */
    sig_str_t canonical_request = {.data = canonical_request_buf};
    get_canonical_request(sig_params, &canonical_request);

    /* Task 2: Create a string to sign */
    sig_str_t string_to_sign = {.data = string_to_sign_buf};
    get_string_to_sign(date, &canonical_request, &string_to_sign);

    /* Task 3: Calculate the signature */
    char signed_msg_buf[HMAC_MAX_MD_CBLOCK] = {0};
    sig_str_t signed_msg = {.data = signed_msg_buf};

    /* Get HMAC SHA256 */
    HMAC(EVP_sha256(),
        (unsigned char*)sig_params->secret.data, sig_params->secret.len,
        (unsigned char*)string_to_sign.data, string_to_sign.len,
        (unsigned char*)signed_msg.data, &signed_msg.len);
    sig_str_t signature = {.data = str};
    get_hexdigest(&signed_msg, &signature);
    str += signature.len;
    *str = '\0';

    sig_headers_add(&sig_params->headers, SIG_HEADER_AUTHORIZATION, auth_header);
    return SIG_OK;
}
